
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { Module, ModuleCategory, ModuleDepsGraph } from './moduleComponent';
import path from 'path';
import fs from 'fs';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { DependsNode } from 'arkanalyzer/lib/core/graph/DependsGraph';
import { OH_PACKAGE_JSON5 } from 'arkanalyzer/lib/core/common/EtsConst';
import { OHPackageConfig } from './OHPackageConfig';

const logger = Logger.getLogger(LOG_MODULE_TYPE.TOOL, 'moduleDeps');

export class ModuleDeps {
    private modulePool: Map<string, DependsNode<Module>> = new Map();
    private static instance: ModuleDeps;

    private constructor() {
    }

    public static getInstance(): ModuleDeps {
        if (!this.instance) {
            this.instance = new ModuleDeps();
        }
        return this.instance;
    }

    /**
     * 处理模块路径的标准化并添加到modulePool
     * @param src 源模块节点
     * @returns 标准化后的模块路径，如果模块已存在则返回undefined
     */
    private processModulePath(src: DependsNode<Module>): string | undefined {
        const modulePath = src.getNodeAttr().name;
        try {
            const canonicalModulePath = fs.realpathSync(modulePath);
            if (this.modulePool.has(canonicalModulePath)) {
                return undefined;
            }
            this.modulePool.set(canonicalModulePath, src);
            return canonicalModulePath;
        } catch (e) {
            if (this.modulePool.has(modulePath)) {
                return undefined;
            }
            this.modulePool.set(modulePath, src);
            return modulePath;
        }
    }

    /**
     * 处理模块的依赖关系
     * @param depsGraph 依赖图
     * @param src 源模块节点
     * @param modulePath 模块路径
     * @param projectConfig 项目配置
     * @returns OHPackageConfig对象或undefined
     */
    private processModuleDependencies(depsGraph: ModuleDepsGraph, src: DependsNode<Module>, 
                                     modulePath: string, projectConfig?: OHPackageConfig): OHPackageConfig | undefined {
        const moduleOhPkgPath = path.join(modulePath, OH_PACKAGE_JSON5);
        if (!fs.existsSync(moduleOhPkgPath)) {
            logger.debug(`No ${OH_PACKAGE_JSON5} found for module: ${modulePath}`);
            return undefined;
        }

        try {
            // 获取模块配置
            const moduleOhPkg = this.getModulePackageConfig(moduleOhPkgPath, projectConfig);
            if (!moduleOhPkg) {
                return undefined;
            }
            
            // 更新项目配置（如果是第一次）
            if (!projectConfig) {
                projectConfig = moduleOhPkg;
            }
            
            // 处理所有依赖节点
            const dstNodes = moduleOhPkg.getDependencyModules();
            dstNodes.forEach((dstNode) => this.addSingleDependency(depsGraph, src, modulePath, dstNode, projectConfig!));
            
            return moduleOhPkg;
        } catch (e) {
            logger.warn(`Failed to process dependencies for ${modulePath}: ${(e as Error).message}`);
            return undefined;
        }
    }
    
    /**
     * 获取模块的包配置
     * @param moduleOhPkgPath 模块配置文件路径
     * @param projectConfig 项目配置（可选）
     * @returns OHPackageConfig对象
     */
    private getModulePackageConfig(moduleOhPkgPath: string, projectConfig?: OHPackageConfig): OHPackageConfig {
        return projectConfig 
            ? OHPackageConfig.fromFile(moduleOhPkgPath, projectConfig) 
            : OHPackageConfig.fromFile(moduleOhPkgPath);
    }
    
    /**
     * 添加单个依赖关系
     * @param depsGraph 依赖图
     * @param src 源模块节点
     * @param modulePath 模块路径
     * @param dstNode 目标依赖节点
     * @param projectConfig 项目配置
     */
    private addSingleDependency(depsGraph: ModuleDepsGraph, src: DependsNode<Module>, 
                              modulePath: string, dstNode: Module, projectConfig: OHPackageConfig): void {
        // 跳过未知类型的依赖
        if (dstNode.kind === ModuleCategory.UNKNOWN) {
            logger.warn(`Skipping unknown dependency for ${modulePath}: ${dstNode.name}`);
            return;
        }
        
        try {
            // 尝试获取规范化路径
            const canonicalDstPath = fs.realpathSync(dstNode.name);
            const dst = this.getOrCreateDependencyNode(depsGraph, canonicalDstPath, 
                                                     { ...dstNode, name: canonicalDstPath }, projectConfig);
            depsGraph.addEdge(src, dst, { kind: 0 });
        } catch (e) {
            // 使用原始名称作为备选
            const dst = this.getOrCreateDependencyNode(depsGraph, dstNode.name, dstNode, projectConfig);
            depsGraph.addEdge(src, dst, { kind: 0 });
        }
    }
    
    /**
     * 获取或创建依赖节点
     * @param depsGraph 依赖图
     * @param nodePath 节点路径
     * @param nodeData 节点数据
     * @param projectConfig 项目配置
     * @returns DependsNode<Module> 对象
     */
    private getOrCreateDependencyNode(depsGraph: ModuleDepsGraph, nodePath: string, 
                                     nodeData: Module, projectConfig: OHPackageConfig): DependsNode<Module> {
        // 检查节点是否已存在
        if (this.modulePool.has(nodePath)) {
            return this.modulePool.get(nodePath)!;
        }
        
        // 创建新节点并递归处理其依赖
        const newNode = depsGraph.addDepsNode(nodePath, nodeData);
        this.addDeps(depsGraph, newNode, projectConfig);
        return newNode;
    }

    /**
     * 添加模块依赖到依赖图
     * @param depsGraph 依赖图
     * @param src 源模块节点
     * @param projectConfig 项目配置（可选）
     * @returns OHPackageConfig对象或undefined
     */
    public addDeps(depsGraph: ModuleDepsGraph, src: DependsNode<Module>, projectConfig?: OHPackageConfig): OHPackageConfig | undefined {
        const modulePath = this.processModulePath(src);
        if (!modulePath) {
            return undefined;
        }
        return this.processModuleDependencies(depsGraph, src, modulePath, projectConfig);
    }
}